"""
-*- coding: utf-8 -*-
@Author  : Link
@Time    : 2023/5/26 16:06
@Site    : 
@File    : TEST_51_DIE.py
@Software: PyCharm
@Remark  : 
"""
import os
import sys
import time
from typing import List

import numpy as np
from threading import Thread

from loguru import logger
from PySide2.QtCore import QTimer, QThread
from PySide2.QtWidgets import QApplication, QProgressBar
from pyqtgraph import GraphicsLayoutWidget
from pyqtgraph.dockarea import *

from BenchCore.bench_interface.bench_interface import BenchCore
from BenchCore.code_interface.ate_code import DtrType
from BenchCore.stdf_interface.stdf_data_class import LimitClass, LimitTypes
from BenchCore.variable_interface.app_variable import AppVariable
from BenchCore.variable_interface.site_variable import SiteVariable
from BenchUtils.func import ui_location
from BenchUtils.log_utils import Print
from BenchUtils.wrapper_interface import Tester, UnitTester
from UNIT_DEMO.DeviceInferface import FakeSmu, FakeDigital
from UNIT_DEMO.ProjectConfig import Slt1Project


class TestDemoCp(BenchCore):
    """
    https://blog.csdn.net/hezhongla0811/article/details/113777203 解决PyTest异常直接打印不暂停问题
    """
    var: SiteVariable = None
    app_var: AppVariable = None

    def setup_class(self, dut: int = 0, socket_count: int = 4) -> bool:
        """
        :param dut: 当前SITE
        :param socket_count: SITE数
        """
        self.socket_count = socket_count
        self.pro = Slt1Project()  # default
        try:

            self.pro.LOGGER_ID = logger.add('D:\\logger.log', level="DEBUG", retention='14 days')
            self.var = SiteVariable(dut)
            assert self.var.dut == dut
            curr_path = os.path.dirname(os.path.realpath(__file__))
            self.app_var = AppVariable(self.pro, curr_path=curr_path)
            assert self.app_var.ready is True

            self.smu = FakeSmu()
            assert self.smu.connect() is True
            self.digital = FakeDigital()
            assert self.digital.connect() is True

        except AssertionError:
            return False
        self.var.variable_update()
        return True

    def teardown_class(self):
        """
        post pytest
        TODO: 如果需要生成HBR和SBR,则需要做统计,待Update
        :return:
        """
        if self.app_var is None:
            return
        if not self.app_var.ready:
            return
        self.app_var.save_hbr(self.var)
        self.app_var.save_sbr(self.var)
        self.app_var.lot_end()
        self.app_var.print_bin()
        # =============================================== For Debug Summary
        logger.remove(self.pro.LOGGER_ID)  # 取消Logger对文件的占用

    def test_lot_start(self, info: dict = None):
        """
        setup后读取lot信息, 从subcon获取
        :info: 从SubCon获取的Mir相关信息
        :return:
        """
        if self.app_var.mir is not None:
            return
        if isinstance(info, dict):
            self.app_var.gen_mir(**info)
        else:
            self.app_var.gen_temp_mir()
        if self.app_var.mir is None:
            raise Exception
        self.app_var.lot_begin(self.var.dut)
        if self.pro.LOGGER_ID is not None:
            logger.remove(self.pro.LOGGER_ID)
        logger_name = self.pro.LOG_SAVE_NAME.format(**self.app_var.mir.to_dict(), DUT=self.var.dut)
        self.pro.LOGGER_ID = logger.add(os.path.join(self.pro.STDF_SAVE_PATH, logger_name),
                                        level="DEBUG", retention='14 days')
        for each in Print.LOT_INFO(self.app_var.mir, string=True).split("\n"):
            self.Dtr(each, tp=DtrType.Success)
        self.app_var.save_dtr(self.var)

    def test_per_run(self):
        """
        侦听从 Handle 或是 Probe的信号和数据, ~
        :return:
        """
        if self.app_var is None:
            return
        if not self.app_var.ready:
            return
        # ==============================
        # time.sleep(2) // 从Handle 或是 Probe侦听到坐标数据
        # ==============================
        self.app_var.save_pir(
            self.var
        )
        self.var.variable_update(X_COORD=1, Y_COORD=1)
        return True

    def test_post_run(self):
        """
        数据保存
        :return:
        """
        if self.var.prr is None:
            return
        self.app_var.post_run(self.var)
        return not self.var.prr.PART_FLG.part_failed_flag()

    @Tester(
        over_on=True
    )
    def test_print_bin(self):
        """
        将最后的bin值给到下位机
        :return:
        """
        if self.var.prr.PART_FLG.part_failed_flag():
            Print.FAIL(self.var.dut)
        else:
            Print.PASS(self.var.dut)
            if 1 not in self.app_var.hbr:
                self.app_var.hbr[1] = [1, "PASS", "P", 1]
            else:
                self.app_var.hbr[1][3] += 1
            if 1 not in self.app_var.sbr:
                self.app_var.sbr[1] = [1, "PASS", "F", 1]
            else:
                self.app_var.sbr[1][3] += 1

    @Tester()
    def test_power_short(self):
        pass

    @Tester(
        over_on=True,
        # loop_times=100,
    )
    def test_idd_1(self):
        vdd1_volt = self.smu.mv(4900, 5100)
        self.JudgeParameters(vdd1_volt, "VDD_V_5V", "VDD1")
        self.Dtr("over_on=True")

    @Tester(
        over_on=True,
    )
    def test_idd_2(self):
        vdd2_volt = self.smu.mv(5100, 5400)
        self.JudgeParameters(vdd2_volt, "VDD_V_5V", "VDD2")
        self.Dtr("over_on=True")

    @Tester(
        over_on=True,
    )
    def test_idd_3(self):
        vdd3_volt = self.smu.mv(4800, 5900)
        self.JudgeParameters(vdd3_volt, "VDD_V_5V", "VDD3")
        self.Dtr("over_on=False", tp=DtrType.Warning)

    @Tester(
        over_on=True,
    )
    def test_idd_4(self):
        vdd4_volt = self.smu.mv(4800, 5200)
        self.JudgeParameters(vdd4_volt, "VDD_V_5V", "VDD4")
        self.Dtr("over_on=False", tp=DtrType.Warning)

    @Tester(
        over_on=True,
    )
    def test_scan_pattern(self):
        dft_result = self.digital.get_result()
        self.JudgeParameters(
            dft_result,
            LimitClass(
                TEST_NUM=10010,
                TEST_TXT="DFT_SCAN",
                L_LIMIT=1,
                H_LIMIT=1,
                UNITS="BIT",
                LO_LIMIT_TYPE=LimitTypes.GE,
                HI_LIMIT_TYPE=LimitTypes.LE,
                SOFT_BIN=11,
                SOFT_BIN_NAME="DFT_FAIL",
                HARD_BIN=3,
                HARD_BIN_NAME="DFT_FAIL",
            )
        )
        self.Dtr("over_on=False", tp=DtrType.Debug)

    @Tester(
        over_on=False,
    )
    def test_power_on(self):
        self.smu.power_on()
        self.Dtr("SMU POWER ON", tp=DtrType.Warning)

    @Tester(
        over_on=True,
    )
    def test_power_off(self):
        self.smu.power_off()
        self.Dtr("SMU POWER OFF", tp=DtrType.Warning)

    @UnitTester(
        loop_times=20,
        fail_break=False,
    )
    def test_case(self):
        """
        各自的DUT进程拥有自己的固定流程
        """
        if not self.test_power_short():
            return
        self.test_power_on()  # 上电
        self.test_idd_1()  # 量电压
        self.test_idd_2()  # 量电压
        self.test_idd_3()  # 量电压
        self.test_idd_4()  # 量电压
        self.test_scan_pattern()  # 跑激励
        self.test_power_off()  # 下电

    @UnitTester(
        loop_times=20,
        fail_break=True,
    )
    def test_switch_case(self):
        """
        根据简单的判断逻辑来对不同DUT的TESTFLOW进行微调
        """
        if not self.test_power_short():  # 是否有短路
            return
        self.test_power_on()  # 上电
        self.test_idd_1()  # 量电压
        if self.var.dut == 1:
            self.test_idd_2()  # 量电压
        else:
            self.test_idd_3()  # 量电压
        self.test_idd_4()  # 量电压
        self.test_scan_pattern()  # 跑激励
        self.test_power_off()  # 下电

    @Tester(
        over_on=True,
    )
    def test_multi_thread(self):
        """
        TODO: 在测试进程中使用多线程
        """

        class StationParams:
            have_fail: bool = False
            result: bool = 1

        class UnitParams:
            result: int = 1
            times: int = 50
            device: str = "DEVICE_1"

        def loop_test(u_params: UnitParams, g_params: StationParams):
            for i in range(u_params.times):
                if g_params.have_fail:
                    break
                self.Dtr("{} Loop Times {}".format(u_params.device, i), tp=DtrType.Warning)
                # if i == 89:
                #     g_params.have_fail = False
                #     g_params.result = 0
                time.sleep(0.03)

        class WrDeviceThread(Thread):
            result: int = None

            def __init__(self, u_params: UnitParams, g_params: StationParams):
                Thread.__init__(self)
                self.u_params = u_params
                self.g_params = g_params

            def run(self):
                loop_test(self.u_params, self.g_params)

            def get_result(self) -> UnitParams:
                return self.u_params

        global_params = StationParams()
        thread_list = []
        dsk_path_list = ["DEVICE_1", "DEVICE_2", "DEVICE_3"]
        for index, device in enumerate(dsk_path_list):
            unit_params = UnitParams()
            unit_params.device = device
            unit_params.index = index
            thread = WrDeviceThread(unit_params, global_params)
            thread.start()
            thread_list.append(thread)
        for thread in thread_list:
            thread.join()
        for index, thread in enumerate(thread_list):
            unit_params = thread.get_result()
            if unit_params.result == -1:
                self.Dtr("FAIL_DEVICE: {}".format(dsk_path_list[index]), tp=DtrType.Warning)
            else:
                self.Dtr("PASS_DEVICE: {}".format(dsk_path_list[index]), tp=DtrType.Success)
        self.JudgeParameters(global_params.result, "IIC_TEST")

    @UnitTester(
        loop_times=3,
        fail_break=False,
    )
    @Tester(
        over_on=True,
    )
    def test_qt_timer_ui(self):
        """
        使用QT QTimer定时器来实现界面非阻塞式编程
        TODO: 只是演示示例代码, 工程必须要自己封装Ui类
        """

        def iv_sweep_qt_timer():
            """
            使用qt QTimer定时函数
            :return:
            """
            RunParams.data[RunParams.ptr] = np.random.normal()
            RunParams.ptr += 1
            if RunParams.ptr >= RunParams.data.shape[0]:
                q_timer.stop()
                iv_sweep_done()
            else:
                progressbar.setValue(RunParams.ptr + 1)
                p1.plot(RunParams.data, clear=True)

        def iv_sweep_done():
            """
            QTimer定时函数结束后关闭Ui
            :return:
            """
            QTimer.singleShot(1000, lambda: (win.close(), progressbar.close()))  # 模拟对数据进行操作和分析后得到数据

        class RunParams:
            plots = 200
            data = np.zeros(plots)
            ptr = 0

        app = QApplication.instance()
        if app is None:
            app = QApplication(sys.argv)
        win = GraphicsLayoutWidget()
        p1 = win.addPlot(title="TEST_QT_PLOT_SITE{}".format(self.var.dut))
        progressbar = QProgressBar()
        progressbar.setMaximum(RunParams.plots)
        progressbar.move(*ui_location(self.socket_count, self.var.dut, progressbar.width(), progressbar.height(), 5, 5))
        q_timer = QTimer()
        q_timer.timeout.connect(iv_sweep_qt_timer)
        q_timer.start(10)
        win.move(*ui_location(self.socket_count, self.var.dut, win.width(), win.height(), 20, 20))
        win.show()
        progressbar.show()
        app.exec_()
        self.JudgeParameters(
            1,
            LimitClass(
                TEST_NUM=11010,
                TEST_TXT="IV_SWEEP_RESULT",
                L_LIMIT=1,
                H_LIMIT=1,
                UNITS="BIT",
                LO_LIMIT_TYPE=LimitTypes.GE,
                HI_LIMIT_TYPE=LimitTypes.LE,
                SOFT_BIN=12,
                SOFT_BIN_NAME="IV_SWEEP",
                HARD_BIN=4,
                HARD_BIN_NAME="IV_SWEEP",
            ))

    @UnitTester(
        loop_times=3,
        fail_break=False,
    )
    @Tester(
        over_on=True,
    )
    def test_qt_thread_ui(self):
        """
        使用QT的多线程模块来实现多线程编程
        TODO: 只是演示示例代码, 工程必须要自己封装Ui类
        """
        from PySide2.QtWidgets import QMainWindow, QTextBrowser
        from PySide2.QtCore import Signal

        class RunParams:
            plots = 200
            times = 50

        class WorkThread1(QThread):
            """
            模拟读取波形数据
            """
            flushChart: Signal = Signal(object)

            def __init__(self, parent=None):
                super(WorkThread1, self).__init__(parent)
                self.data = np.random.normal(size=(10, 1000))
                self.ptr = 0

            def run(self) -> None:
                for i in range(RunParams.plots):
                    time.sleep(0.02)
                    data: np.array = self.data[self.ptr % 10]
                    self.flushChart.emit(data)
                    self.ptr += 1

        class WorkThread2(QThread):
            """
            模拟文本数据读取
            """
            flushString: Signal = Signal(str)

            def __init__(self, parent=None):
                super(WorkThread2, self).__init__(parent)

            def run(self) -> None:
                for i in range(RunParams.plots):
                    time.sleep(0.02)
                    self.flushString.emit("WorkThread 2 String->{}".format(i))

        class WorkThread3(QThread):
            """
            QT线程中调用Python线程
            """

            def __init__(self, parent=None):
                super(WorkThread3, self).__init__(parent)

            @staticmethod
            def loop_test(device: str, times: int):
                for i in range(int(times)):
                    self.Dtr("{} Loop Times {}".format(device, i), tp=DtrType.Warning)
                    time.sleep(0.03)

            def run(self) -> None:
                dsk_path_list = ["DEVICE_1", "DEVICE_2", "DEVICE_3"]
                th_list: List[Thread] = []
                for index, device in enumerate(dsk_path_list):
                    th = Thread(target=WorkThread3.loop_test, args=(device, RunParams.times,))
                    th_list.append(th)
                for th in th_list:
                    th.start()
                for th in th_list:
                    th.join()

        class MainUi(QMainWindow):
            def __init__(self, var: SiteVariable):
                super(MainUi, self).__init__()
                self.var = var
                self.area = DockArea()
                self.setCentralWidget(self.area)

                self.text_browser = QTextBrowser(self)
                dock_text_browser = Dock("TextBrowser", size=(100, 400))
                dock_text_browser.addWidget(self.text_browser)

                self.area.addDock(dock_text_browser, "left")
                self.plot_widget = GraphicsLayoutWidget()
                dock_chart = Dock("Chart", size=(200, 400))
                dock_chart.addWidget(self.plot_widget)
                self.area.addDock(dock_chart, "right")

                self.p1 = self.plot_widget.addPlot(title="TEST_QT_PLOT_SITE{}".format(self.var.dut))
                self.resize(900, 500)

                self.work1 = WorkThread1(self)
                self.work2 = WorkThread2(self)
                self.work3 = WorkThread3(self)
                self.work2.flushString.connect(lambda string: self.text_browser.append(string))
                self.work1.flushChart.connect(lambda data: self.p1.plot(data, clear=True))
                self.work1.finished.connect(self.finished_th)
                self.work2.finished.connect(self.finished_th)
                self.work3.finished.connect(self.finished_th)

                QTimer.singleShot(1000, self.run_all)

            def run_all(self):
                """
                启动线程
                """
                self.work1.start()
                self.work2.start()
                self.work3.start()

            def finished_th(self):
                if all(
                        [
                            self.work1.isFinished(),
                            self.work2.isFinished(),
                            self.work3.isFinished(),
                        ]
                ):
                    QTimer.singleShot(500, self.close)

        app = QApplication.instance()
        if app is None:
            app = QApplication(sys.argv)
        win = MainUi(self.var)
        win.move(*ui_location(self.socket_count, self.var.dut, win.width(), win.height(), 20, 20))
        win.show()
        app.exec_()
        self.JudgeParameters(1, "IIC_TEST")
